import common 
from myeth import MyAccountHelper,MyContract,MyTransaction
from eth_keys import keys
from eth_account.local import LocalAccount
from secure_channel import NonceChannel_DU,NonceChannel_DO
import aiohttp
from aiohttp import web
import time
# import requests
from common.strtools import hex_add_0x
from db import DBHelper
from abe import MyABE
from encrypter import MyAES
from binascii import a2b_hex,b2a_hex,a2b_base64,b2a_base64
import json
from myweb3 import MyWeb3DU,MyWeb3DO

class AppEnv:
    def __init__(self):
        self.acct_helper = MyAccountHelper
        self.__initialize()
        self.init_contracts_with_local_files()

    def __initialize(self):
        self.acct_priv_key: keys.PrivateKey = None
        self.account: LocalAccount = None
        self.acct_public_key: keys.PublicKey = None 
        self.acct_address:str = None
        self.__my_DO_contract:MyContract = None
        self.__my_DU_contract:MyContract = None
        self.__DO_contract = None
        self.__DU_contract = None
        self.send_channel_dct = dict() # as DU，DU->DO
        self.received_requset_dct = dict() # as DO, DO<-DU
        # ABE
        self.ABE_public_key = None 
        self.ABE_master_key = None
        self.ABE_owner_priv_key = None
        # self.shared_keys = dict()
        # ===
        

    def get_shared_key(self,acct_address:str):
        # TODO 最好从数据库里获取
        # return self.shared_keys.get(hex_add_0x(acct_address),None)
        # TODO 判空
        shared_key = DBHelper.get_SharedKey(
            hex_add_0x(self.acct_address),
            hex_add_0x(acct_address)
        )
        return shared_key

    def _check_log_in(self):
        '''检查是否登入
        '''
        assert self.account is not None

    def init_contracts_with_local_files(self):
        self.__init_DO_contract()
        self.__init_DU_contract()
        
    def __init_DO_contract(self):
        self.__my_DO_contract = MyContract.from_src_file(common.args.contractFile,is_DO_contract=True)

    def __init_DU_contract(self):
        self.__my_DU_contract = MyContract.from_src_file(common.args.contractFile,is_DO_contract=False)

    def deploy_DO_contract(self,search_in_DB=True):
        """部署 DO 智能合约
        如果 search_in_DB 为 True，会先在数据库中检索已经部署的智能合约
        如果找到了，则步重复部署；否则进行部署
        """
        self._check_log_in()
        if search_in_DB:
            if not self.fetch_DO_contract_from_DB():
                self.__init_DO_contract()
                self.__my_DO_contract.deploy_contract(self.account)
        else:
            self.__init_DO_contract()
            self.__my_DO_contract.deploy_contract(self.account)
        

    def deploy_DU_contract(self,search_in_DB=True):
        """部署 DU 智能合约
        如果 search_in_DB 为 True，会先在数据库中检索已经部署的智能合约
        如果找到了，则步重复部署；否则进行部署
        """
        self._check_log_in()
        if search_in_DB:
            if not self.fetch_DU_contract_from_DB():
                self.__init_DU_contract()
                self.__my_DU_contract.deploy_contract(self.account)
        else:
            self.__init_DU_contract()
            self.__my_DU_contract.deploy_contract(self.account)

    def fetch_DO_contract_from_DB(self):
        self._check_log_in()
        _mycon = MyContract.construct_MyContract_from_DB(
            self.acct_address,
            is_DO_contract = True
        )
        if _mycon is not None:
            self.__my_DO_contract = _mycon
            return True
        return False

    def fetch_DU_contract_from_DB(self):
        self._check_log_in()
        _mycon = MyContract.construct_MyContract_from_DB(
            self.acct_address,
            is_DO_contract = False
        )
        if _mycon is not None:
            self.__my_DU_contract = _mycon
            return True
        return False

    # def get_DO_contrat(self):
    #     self._check_log_in()
    #     if self.__DO_contract is not None:
    #         return self.__DO_contract
    #     elif self.__my_DO_contract is not None:
    #         my_addr = hex_add_0x(self.acct_address)
    #         contract_record = DBHelper.get_contract_with_acct_addr(my_addr,is_DO_contract=True)
    #         self.__DO_contract = self.get_deployed_contract(
    #             contract_record.contract_address,
    #             contract_record.contract_abi
    #         )
    #     else:
    #         raise Exception("my_DO_contract 没有初始化")

    # def get_DU_contrat(self):
    #     self._check_log_in()
    #     if self.__DU_contract is not None:
    #         return self.__DU_contract
    #     elif self.__my_DU_contract is not None:
    #         my_addr = hex_add_0x(self.acct_address)
    #         contract_record = DBHelper.get_contract_with_acct_addr(my_addr,is_DO_contract=False)
    #         self.__DU_contract = self.get_deployed_contract(
    #             contract_record.contract_address,
    #             contract_record.contract_abi
    #         )
    #     else:
    #         raise Exception("my_DU_contract 没有初始化")

    def get_deployed_contract(self,contract_address:str,abi):
        return MyContract.get_deployed_contract(contract_address,abi)



    # def deploy_contract(self,sol_src_path:str):
    #     self._check_log_in()
    #     self.__my_DO_contract = MyContract(sol_src_path)
    #     self.__my_DO_contract.deploy_contract(self.account)

    def get_acct_address(self):
        self._check_log_in()
        return self.acct_address

    def send_transaction(self,data:bytes):
        """发送以data为数据的transaction，返回transaction的hash
        """
        self._check_log_in()
        return MyTransaction.send_txn(self.account,data)

    def get_transaction_data(self,txn_hash:str)->bytes:
        """根据 txn_hash 获取对应 transaction 的 data 部分
        """
        self._check_log_in()
        data = MyTransaction(txn_hash).get_data()
        return data

    # 论文中的第三步（好吧，我不知道要取什么名字）
    # DU 向 DO 发注册请求(向目标用户特定的ip:port发送)
    # 与此同时，做一部分建立安全通道的工作（应该是只是临时安全通道）
    # 对方的 address 是向对方发请求获得的
    async def the_third_step_in_the_paper(
        self,
        http_address_for_getting_his_acct_address,
        http_address_for_sending_request,
        ex_msg_to_send,
    )-> web.Response:
        self._check_log_in()
        his_acct_address = None
        async with aiohttp.request(
            "GET",
            http_address_for_getting_his_acct_address
        ) as resp:
            his_acct_address = await resp.text()
        assert his_acct_address is not None
        # 作为 DU
        c = NonceChannel_DU(
            http_address_for_sending_request,
            self.acct_priv_key,
            his_acct_address
        )
        # 先存起来
        # TODO 暂时先不考虑重复存的问题
        # +++++++++++++++++++++
        self.send_channel_dct[hex_add_0x(his_acct_address)] = {
            "time":time.time(),
            "channel":c
        }
        # +++++++++++++++++++++
        resp = await c.send_request(
            ex_msg_to_send = ex_msg_to_send
        )
        return resp

    # DO 收到 DU 的请求
    # 从请求中提取出 json， 等待人为处理
    async def save_request_json_in_third_step(
        self,
        request:web.Request,
        http_address_for_return:str
    ):
        requestJson = await request.json()
        his_address = requestJson["address"]
        # 先存起来
        # +++++++++++++++++++++
        # [requestJson]
        # send_info = {
        #     "address":self.my_address,
        #     "DH_PK":self.my_pub_key,
        #     "ex_msg":ex_msg_to_send
        # }
        # +++++++++++++++++++++
        self.received_requset_dct[hex_add_0x(his_address)]={
            "time":time.time(),
            "request":requestJson,
            "http_address_for_return":http_address_for_return
        }
        

    # 论文第五步。。23333（不知道怎么命名，别打我 hhhhhh）
    # DO 向 DU 返回一些信息
    # 我准备写成 第三步里 DU 向 DO 发 POST 请求，然后 DO 相应请求
    # 所以下面这个协程要与 webapp 的 controller 配套用
    # 而且 DO 得同意（人工同意）DU 的请求才行，不止下面这一步
    async def the_fifth_step_in_the_paper(
        self,
        http_address_for_sending_response,
        requestJson,
        attributes
    )-> web.Response:
        self._check_log_in()
        # DO channel
        c = NonceChannel_DO(
            http_address_for_sending_response,
            self.acct_priv_key,
        )
        # =======================
        # ABE private key
        
        _ABE_priv_key = MyABE.gen_user_priv_key(
            public_key = self.ABE_public_key,
            master_key = self.ABE_master_key,
            user_attrs_list = attributes
        )
        _ABE_send_json = {
            "private_key":_ABE_priv_key,
            "public_key":self.ABE_public_key,
        }
        _ABE_send_json_str = json.dumps(_ABE_send_json)

        # 先生成 shared key, 用于 AES 加密 ABE 私钥
        await c.gen_shared_key(requestJson)
        _AES = MyAES(c.get_shared_key())
        encrypted_ABE_send_json = _AES.encrypt(_ABE_send_json_str)
        # 加密有关信息，存到 transaction
        ABE_key_txn_id:str = MyTransaction.send_txn(
            account = self.account,
            # data = encrypted_ABE_send_json.encode("utf-8") # 已经是二进制的
            data = encrypted_ABE_send_json,
            ret_str_format=True # 转16进制字符串
        )
        # =======================
        
        # # +++++++++++++++++
        contract_address = self.__my_DO_contract.contract_address
        contract_abi = self.__my_DO_contract.contract_abi
        contract_src = self.__my_DO_contract.src
        # # +++++++++++++++++

        # ABE_key_txn_id = "test_txn_id"
        # contract_address = "test_contract_addr"
        # contract_abi = "test_contract_abi"
        # contract_src = "test_contract_src"

        resp = await c.respond(
            ABE_key_txn_id,
            contract_address,
            contract_abi,
            contract_src
        )
        # 新增 shared_key
        # self.shared_keys[hex_add_0x(c.his_address)]=c.get_shared_key()
        DBHelper.save_SharedKey(
            hex_add_0x(self.acct_address),
            hex_add_0x(c.his_address),
            c.get_shared_key()
        )
        return resp

    # DU 在第五步中获取返回结果
    async def DU_receive_response_in_the_fifth_step(self,resp_request:web.Request):
        respJson = await resp_request.json()
        acct_addr = hex_add_0x(respJson["address"])
        _c = self.send_channel_dct.get(acct_addr,None)
        assert _c is not None
        c = _c["channel"]
        resp_json_content = await c.receive_response(respJson)
        # 增加 shared key
        # self.shared_keys[hex_add_0x(c.his_address)]=c.get_shared_key()
        DBHelper.save_SharedKey(
            hex_add_0x(self.acct_address),
            hex_add_0x(c.his_address),
            c.get_shared_key()
        )
        """
        respJson 在 communicate.py 对应代码为
        {
            "address":self.my_address,
            "aggree":True,
            "DH_PK":self.my_pub_key,
            "content":encrypted_content.decode("utf-8")
        }
        """
        respJson["content"] = resp_json_content
        return respJson
        
        
    def sign_in_with_stored_key(self,accountAddress: str, keyPassword: str = ""):
        
        acct_priv_key = self.acct_helper.get_private_key(
            accountAddress,keyPassword
        )
        self.__sign_in_common_part(acct_priv_key)

    def sign_in_with_key_str(self,priv_key_hex_str:str):
        acct_priv_key = self.acct_helper.get_private_key_from_str(
            priv_key_hex_str
        )
        self.__sign_in_common_part(acct_priv_key)
        

    def __sign_in_common_part(self,acct_priv_key):
        if self.acct_priv_key is not None:
            if self.acct_priv_key._raw_key == acct_priv_key._raw_key:
                # 此时认为重复登入了，完全不必要
                self.__ex_sign_in_init()
                return
        # ============
        # 重新登入后，要重置
        # self.__my_DO_contract = None
        # self.__my_DU_contract = None
        self.__initialize()
        # ============
        self.acct_priv_key = acct_priv_key
        self.acct_public_key = self.acct_priv_key.public_key
        self.acct_address = hex_add_0x(self.acct_public_key.to_checksum_address())
        self.account = self.acct_helper.get_account_from_private_key(
            self.acct_priv_key
        )
        self.__ex_sign_in_init()
        

    def __ex_sign_in_init(self):
        # 初始化 ABE 相关的 keys
        self.init_ABE_owner_keys()
        self.init_ABE_owner_priv_key()
        # 部署智能合约（默认会检查是否已经部署）
        self.deploy_DO_contract()
        self.deploy_DU_contract()

    def sign_up_with_keyword(self,keyword:str):
        return self.acct_helper.new_account(keyword)


    def init_ABE_owner_keys(self):
        self._check_log_in()
        # TODO
        try:
            _ABEOwnerKeyRecord = DBHelper.get_ABEOwnerKey(self.acct_address)
        except AssertionError:
            # 没找到，认为没生成过
            public_key,master_key = MyABE.setup()
            DBHelper.save_ABEOwnerKey(
                account_address = self.acct_address,
                master_key = master_key,
                public_key = public_key,
            )
            _ABEOwnerKeyRecord = DBHelper.get_ABEOwnerKey(self.acct_address)
        # 
        self.ABE_master_key = _ABEOwnerKeyRecord.master_key
        self.ABE_public_key = _ABEOwnerKeyRecord.public_key

    def init_ABE_owner_priv_key(self):
        self._check_log_in()
        # 拥有最高权限的 ABE private key
        self.ABE_owner_priv_key = MyABE.gen_user_priv_key(
            public_key = self.ABE_public_key,
            master_key = self.ABE_master_key,
            user_attrs_list = [common.args.HighestAuthorityAttr]
        )
        # 存到数据库
        DBHelper.save_ABEUserKey(
            user_acct_addr = self.acct_address, # 都是自己
            master_acct_addr = self.acct_address, # 都是自己
            private_key = self.ABE_owner_priv_key,
            public_key = self.ABE_public_key
        )

    def new_MyWeb3DU(self):
        self._check_log_in()
        account = self.account
        contract = self.__my_DU_contract.contract
        _w3DU = MyWeb3DU(account,contract)
        return _w3DU

    def new_MyWeb3DO(self):
        self._check_log_in()
        account = self.account
        contract = self.__my_DO_contract.contract
        _w3DU = MyWeb3DO(account,contract)
        return _w3DU

